余烬缀记

JavaScript 面向对象的几种设计模式

摘抄自红皮书 [JavaScript 高级程序设计]

# 工厂模式

Jclaol

工厂模式抽象了创建具体对象的过程

代码例子

function createPerson(name, age, job){
   	var o = new Object();
	o.name = name;
	o.age = age;
	o.job = job;
	o.sayName = function() {
		alert(this.name);
	};
	return o;
}
var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

它解决了创建多个相似对象的问题,但没有解决对象识别的问题 (即怎样知道一个对象的类型).

# 构造函数模式

重写上面例子

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function() {
      alert(this.name);
    };
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

与上面的代码不同处:

  • 没有显式的创建对象;
  • 直接将属性和方法赋给 this 对象
  • 没有 return 语句

按照惯例构造函数始终以一个大写字母开头,非构造函数则使用小写字母开头。

使用new操作符创建构造函数的实例。

使用new实例化这种方式调用构造函数会经历 4 个步骤

  1. 创建一个新对象;
  2. 将构造函数的作用域赋给新对象 (因此 this 指向了这个新对象)
  3. 执行构造函数中的代码 (为这个新对象添加属性)
  4. 返回新对象

每个构造函数的实例对象都一个constructor属性,这个属性指向构造函数

构造函数的问题:

  • 每个构造函数里面的函数在被实例化时会被重新创建,使用更多开销
  • 如果将构造函数里面的函数放在全局作用域上会变得大材小用 (一个全局作用域的函数只被某个对象调用),如果需要对个方法那么要定义多个在全局作用域的函数

# 原型模式

只要创建了一个新函数,就会根据特定规则为该函数创建一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以有特定类型的所有势力共享的属性和方法。在默认情况下,所有原型对象都会自动获得一个constructor属性

使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法

代码示例:

function Person() {}
	Person.prototype.name = "Nicholas";
	Person.prototype.age = "18";
	Person.prototype.job = "Software Engineer";
	Person.prototype.sayName = function() {
  	alert(this.name);
}
var person1 = new Person();

person1.sayName(); // "Nicholas"

var person2 = new Person();

person2.sayName(); // "Nicholas"

alert(person1.sayName === person2.sayName); // true

原型对象模式的问题:

  • 省略了为构造函数传递初始化参数这个环节,所有示例都取得相同的属性值。
  • 共享问题,实例化对象修改在原型中属性时会导致其他指向这个原型的实例化对象属性发生变化

# 组合使用构造函数模式和原型模式

最常见的定义方式,构造函数模式用来定义实例属性,原型定义方法和共享属性。每个实例对象都要一份属于自己的实例属性但同时有共享着方法。

代码示例:

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
}
Person.prototype = {
    construct:Person,
    sayName:function() {
      alert(this.name);
    }
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 22, "Doctor");

# 动态原型模式

将所有信息封装在构造函数中,而通过在构造函数中初始化原型 (可以通过判断等来绝对是否需要初始化原型)

代码示例:

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    if(typeof this.sayName !== "function"){
        Person.prototype.sayName = function() {
          alert(this.name);
        };
    }
}

使用动态原型模型时,不能使用对象字面量重写原型。原因是在实例对象指向的原型和重写的原型并不相同

# 寄生构造函数模式

这种模式基本实现是创建一个函数,函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。这个函数很像工程模式

代码示例

function Person(name, age, job) {
  var o = new Object();
  o.name = name;
  o.age = age;
  o.job = job;
  o.sayName = function() {
    alert(this.name);
  }
  return o;
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName(); // "Nicholas"

# 稳妥构造函数模式

指没有公共属性,而且也不引用 this 的对象。稳妥对象最适合在安全环境中 (这些环境会禁止使用 this 和 new),或者在防止数据被其他应用程序改动时使用

与寄生构造模式不同点:
- 新创建的对象实例方法不引用 this;
- 不使用 new 操作符调用构造函数

代码示例:

function Person(name, age, job){
    var o = new Object();
    o.sayName = function() {
      alert(name);
    };
    return o;
}
var friend = new Person("Nicholas", 19, "Software Engineer");
friend.sayName(); // "Nicholas"